package net.avcompris.binding.dom.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static org.apache.commons.lang3.StringUtils.substringBefore;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.avcompris.binding.BindConfiguration;
import net.avcompris.binding.Binder;
import net.avcompris.binding.impl.AbstractBinderInvocationHandler;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;

abstract class AbstractDomBinderInvocationHandler extends
		AbstractBinderInvocationHandler<Node> {

	protected AbstractDomBinderInvocationHandler(final Binder<Node> binder,
			final ClassLoader classLoader, final Class<?> clazz,
			final Node rootNode, final BindConfiguration configuration) {

		super(binder, classLoader, clazz, rootNode, Node.class, configuration);
	}

	@Override
	public final void setAttribute(final Node attributeNode,
			@Nullable final Object value) {

		nonNullArgument(attributeNode, "attributeNode");

		if (attributeNode.getNodeType() != Node.ATTRIBUTE_NODE) {
			throw new NotImplementedException("Node is not an Attribute: "
					+ attributeNode);
		}

		if (value == null) {
			throw new NotImplementedException();
		}

		((Attr) attributeNode).setValue(value.toString());
	}

	@Override
	public final Map<String, Object> getAttributes(final Node node) {

		nonNullArgument(node, "node");

		if (node.getNodeType() != Node.ELEMENT_NODE) {
			throw new NotImplementedException("Node is not an Element: " + node);
		}

		final Map<String, Object> map = new HashMap<String, Object>();

		final NamedNodeMap attributes = node.getAttributes();

		final int attributeCount = attributes.getLength();

		for (int i = 0; i < attributeCount; ++i) {

			final Node attribute = attributes.item(i);

			final String attributeName = attribute.getNodeName();
			final String attributeValue = attribute.getNodeValue();

			map.put(attributeName, attributeValue);
		}

		return map;
	}

	@Override
	public final void setAttribute(final Node node, final String name,
			@Nullable final Object value) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		removeAttribute(node, name);

		if (value == null) {
			return;
		}

		((Element) node).setAttribute(name, value.toString());
	}

	@Override
	public final void removeAttribute(final Node node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		if (node.getNodeType() != Node.ELEMENT_NODE) {
			throw new NotImplementedException("Node is not an Element: " + node);
		}

		((Element) node).removeAttribute(name);
	}

	@Override
	public final Iterable<Node> getChildren(final Node node) {

		nonNullArgument(node, "node");

		if (node.getNodeType() != Node.ELEMENT_NODE) {
			throw new NotImplementedException("Node is not an Element: " + node);
		}

		final List<Node> list = new ArrayList<Node>();

		final NodeList children = node.getChildNodes();

		final int childCount = children.getLength();

		for (int i = 0; i < childCount; ++i) {

			final Node child = children.item(i);

			list.add(child);
		}

		return list;
	}

	@Override
	public final void remove(final Node node) {

		nonNullArgument(node, "node");

		node.getParentNode().removeChild(node);
	}

	private Document getOwnerDocument(final Node node) {

		final Document ownerDocument = node.getOwnerDocument();

		return ownerDocument;
	}

	@Override
	public final Node addToChildren(final Node node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		final Element child;

		if (name.contains("[")) {
			
			throw new IllegalArgumentException("Invalid element name: "+name);
			
		}else
		if (name.contains(":")) {

			final String prefix = substringBefore(name, ":");

			final String nsURI = getNamespaceURI(prefix);

			child = getOwnerDocument(node).createElementNS(nsURI, name);

		} else {

			child = getOwnerDocument(node).createElement(name);
		}

		node.appendChild(child);

		return child;
	}

	@Override
	public final void setValue(final Node node, final Object value) {

		nonNullArgument(node, "node");
		nonNullArgument(value, "value");

		node.setTextContent(value.toString());
	}

	@Override
	public final Node getParent(final Node node) {

		return node.getParentNode();
	}

	@Override
	public String getName(final Node node) {

		nonNullArgument(node, "node");

		return node.getNodeName();
	}

	@Override
	public void setName(final Node node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		getOwnerDocument(node).renameNode(node, null, name);
	}

	@Override
	public final void setNode(final Node node, @Nullable final Object value) {

		nonNullArgument(node, "node");

		final Document document = node.getOwnerDocument();

		if (document == null) {

			throw new IllegalArgumentException(
					"node.ownerDocument should not be null");
		}

		final Node parentNode = node.getParentNode();

		if (value != null) {

			if (value instanceof Node) {

				final Node valueNode = (Node) value;

				final Node clonedNode = valueNode.cloneNode(true);

				document.adoptNode(clonedNode);

				// parentNode.appendChild(clonedNode);

				parentNode.insertBefore(clonedNode, node);

				parentNode.removeChild(node);

			} else {

				throw new NotImplementedException("value.type: "
						+ value.getClass());
			}
		}
	}
}
